package com.xndc.netty;

import com.xndc.config.Iec104Config;
import com.xndc.config.NettyConfig;
import com.xndc.netty.iec104.codec.Iec104Decoder;
import com.xndc.netty.iec104.codec.Iec104Encoder;
import com.xndc.netty.iec104.handler.Iec104BusinessHandler;
import com.xndc.netty.iec104.handler.Iec104SessionHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Netty服务器
 */
@Slf4j
@Component
public class NettyServer {

    @Autowired
    private NettyConfig nettyConfig;

    @Autowired
    private Iec104BusinessHandler iec104BusinessHandler;

    @Autowired
    private Iec104Config iec104Config;

    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private Channel serverChannel;

    /**
     * 启动Netty服务器
     */
    @PostConstruct
    public void start() throws Exception {
        log.info("正在启动Netty服务器，配置端口: {}", nettyConfig.getPort());

        // 创建事件循环组
        bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("boss"));
        int workerThreads = nettyConfig.getWorkerThreads();
        if (workerThreads <= 0) {
            workerThreads = Runtime.getRuntime().availableProcessors() * 2;
        }
        workerGroup = new NioEventLoopGroup(workerThreads, new DefaultThreadFactory("worker"));

        // 创建服务器启动引导
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 128)
                .option(ChannelOption.SO_REUSEADDR, true)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childOption(ChannelOption.TCP_NODELAY, true)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        // 判断是否使用IEC 104协议
                        if (iec104Config.isEnabled() && isIec104Connection(ch)) {
                            initIec104Channel(ch);
                        }
                    }
                });

        // 尝试绑定端口，如果失败则尝试其他端口
        int port = nettyConfig.getPort();
        int maxRetry = 10; // 最大尝试次数
        int retryCount = 0;

        while (retryCount < maxRetry) {
            try {
                // 绑定端口并启动服务器
                serverChannel = bootstrap.bind(port).sync().channel();
                log.info("Netty服务器已启动，监听端口: {}", port);
                // 成功启动，更新配置中的端口号
                nettyConfig.setPort(port);
                return;
            } catch (Exception e) {
                if (e instanceof java.net.BindException) {
                    retryCount++;
                    // 尝试下一个端口
                    port++;
                    log.warn("端口 {} 已被占用，尝试使用端口 {} (尝试 {}/{})",
                            nettyConfig.getPort(), port, retryCount, maxRetry);
                } else {
                    // 其他异常直接抛出
                    log.error("Netty服务器启动失败", e);
                    stop();
                    throw e;
                }
            }
        }

        // 所有尝试都失败
        log.error("无法找到可用端口，Netty服务器启动失败");
        stop();
        throw new RuntimeException("无法找到可用端口，尝试了从 " +
                nettyConfig.getPort() + " 到 " + (nettyConfig.getPort() + maxRetry - 1) + " 的所有端口");
    }

    /**
     * 初始化IEC 104协议通道
     */
    private void initIec104Channel(SocketChannel ch) {
        log.info("初始化IEC 104协议通道: {}", ch.remoteAddress());
        ch.pipeline().addLast(
                // 空闲检测处理器
                new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS),

                // IEC 104协议编解码器
                new Iec104Decoder(),
                new Iec104Encoder(),

                // IEC 104会话管理
                new Iec104SessionHandler(),

                // 业务处理器
                iec104BusinessHandler
        );
    }


    /**
     * 检测是否为HTTP请求
     */
    private boolean isHttpRequest(ByteBuf buf) {
        if (buf.readableBytes() < 4) {
            return false;
        }

        // 读取前4个字节检测是否为HTTP请求
        byte[] bytes = new byte[4];
        buf.getBytes(buf.readerIndex(), bytes);
        String header = new String(bytes);

        return header.startsWith("GET ") ||
                header.startsWith("POST") ||
                header.startsWith("PUT ") ||
                header.startsWith("HEAD") ||
                header.startsWith("DELETE") ||
                header.startsWith("OPTIONS");
    }


    /**
     * 判断是否是IEC 104连接
     * 实际应用中可以通过IP地址、端口或连接参数判断
     */
    private boolean isIec104Connection(SocketChannel ch) {
        InetSocketAddress remoteAddress = ch.remoteAddress();
        if (remoteAddress == null) {
            return false;
        }

        String ipAddress = remoteAddress.getAddress().getHostAddress();
        log.debug("检查连接类型: {}", ipAddress);

        // 本地测试环境特殊处理：如果是本地连接(localhost)且是测试/开发环境，默认识别为IEC104连接
        // 这有助于本地开发测试，生产环境应该删除此特例
        if (ipAddress.equals("127.0.0.1") || ipAddress.equals("localhost")) {
            int port = remoteAddress.getPort();
            // 排除一些明显不是IEC104的连接(如浏览器请求)
            if (port > 1024 && port < 65535) { // 使用动态分配的端口范围
                log.info("本地测试连接，识别为IEC104: {}", remoteAddress);
                return true;
            }
        }

        // 生产环境应该从配置中判断
        // 检查是否在IEC 104设备配置中
        for (Iec104Config.DeviceMapping device : iec104Config.getDevices()) {
            String deviceId = device.getDeviceId();
            if (deviceId != null) {
                // 检查IP地址是否包含在设备ID中
                if (deviceId.contains(ipAddress)) {
                    return true;
                }

                // 或者设备ID是否以IP地址开头(更常见的设置方式)
                if (ipAddress.contains(deviceId) ||
                        (deviceId.startsWith("STATION_") && (deviceId.endsWith(ipAddress) ||
                                deviceId.contains(ipAddress.replace(".", "_"))))) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * 关闭Netty服务器
     */
    @PreDestroy
    public void stop() {
        log.info("正在关闭Netty服务器...");

        if (serverChannel != null) {
            serverChannel.close();
        }

        if (bossGroup != null) {
            bossGroup.shutdownGracefully();
        }

        if (workerGroup != null) {
            workerGroup.shutdownGracefully();
        }

        log.info("Netty服务器已关闭");
    }
} 